iT邦幫忙

2022 iThome 鐵人賽

DAY 29
1
Web 3

從以太坊白皮書理解 web 3 概念系列 第 30

從以太坊白皮書理解 web 3 概念 - Day29

  • 分享至 

  • xImage
  •  

從以太坊白皮書理解 web 3 概念 - Day29

使用 Geth 與 puppeth 來建立私有鏈

今天將會使用 Geth 與 puppeth 來建立私有鏈

並且在上面佈署投票合約做測試

安裝 geth

brew tap ethereum/ethereum
brew install ethereum

確認安裝完成

geth version

建立 Proof-Of-Authority 節點

這裡需要建立兩個結點

建立兩個資料夾 private-chain-node1, private-chain-node2

mkdir private-chain-node1 private-chain-node2

建立帳戶

執行以下指令

geth --datadir private-chain-node1 account new
geth --datadir private-chain-node2 account new

這邊需要個別紀錄所產生出來的 address 做後面使用

設定初始區塊資訊

由於兩個結點的初始區塊是一致的

所以一起使用 puppeth 設定

執行 puppeth

puppeth

會出現一連串設定選擇

選擇配置新的 genesis

What would you like to do? (default = stats)
 1. Show network stats
 2. Configure new genesis
 3. Track new remote server
 4. Deploy network components
> 2

選擇 PoA 只有被授權的節點才可以寫入資料

Which consensus engine to use? (default = clique)
 1. Ethash - proof-of-work
 2. Clique - proof-of-authority
> 2

產生一個 Block 的時間, 選擇預設值 15

How many seconds should blocks take? (default = 15)
> 15

哪些結點有打包 Block 的權利,填入前面的 address

Which accounts are allowed to seal? (mandatory at least one)
> 0xXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
> 0xYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYY
>

設定哪些節點的帳戶預設就有ETH(以太幣),填入前面的 address

Which accounts should be pre-funded? (advisable at least one)
> 0xXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
> 0xYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYY
>

設定私有鏈的 chainID

Specify your chain/network ID if you want an explicit one (default = random)
> 123456

最後選擇匯出成 gensis.json

把配置用在結點

geth --datadir private-chain-node1 init genesis.json
geth --datadir private-chain-node2 init genesis.json

然後個別進入兩個資料夾 private-chain-node1, private-chain-node2

以不同的 port 與 authrpc.port 來執行兩個結點

在 private-chain-node1 執行

geth --datadir ./ --networkid 123456 --port 8000 --nodiscover console

則 private-chain-node2 執行

geth --datadir ./ --networkid 123456 --port 8001 --authrpc.port=8546 --nodiscover console


連結兩個區塊

在 private-chain-node1 執行

node.adminInfo.enode

查出 enode 資訊

在到 private-chain-node2 執行以下指令把 private-chain-node1 加入 peer

admin.addPeer("enode://5990e88cfc948c87a10fa85a98808cc0e46f1a353620f3349fb642f4dce7953b9298174d7043164a556ba9383f29222425c66ac0516a17be3003167427da6f30@127.0.0.1:8000?discport=0")

透過以下指令可以查到連結後的 peer 資訊

admin.peers

撰寫合約

安裝 Truffle 佈署工具

yarn global add truffle

建立專案目錄

mkdir vote_dapp
cd vote_dapp
truffle unbox webpack

撰寫 Vote.sol

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

contract Voting {
  mapping (bytes32 => uint8) public votesReceived;
  bytes32[] public candidateList;

  constructor (bytes32[] memory candidateNames) {
    candidateList = candidateNames;
  }

  function totalVotesFor(bytes32 candidate) public view returns (uint8) {
    require(validCandidate(candidate));
    return votesReceived[candidate];
  }

  function voteForCandidate(bytes32 candidate) public {
    require(validCandidate(candidate));
    votesReceived[candidate]  += 1;
  }

  function validCandidate(bytes32 candidate) view public returns (bool) {
    for(uint i = 0; i < candidateList.length; i++) {
      if (candidateList[i] == candidate) {
        return true;
      }
    }
    return false;
   }
}

修改 migrations/2_deploy_contract.js

var Voting = artifacts.require("Voting");
const web3 = require("web3");
module.exports = function(deployer) {
  deployer.deploy(Voting, [web3.utils.asciiToHex('Rama'), web3.utils.asciiToHex('Nick'), web3.utils.asciiToHex('Jose')]);
};

設定 truffle-config.js

新增 development 設定到 networks

development: {
      host: '127.0.0.1',
      port: 8545,
      network_id: '123456'
    },

修改 complier solc版本為 Contract 指定版本 0.8.0

 compilers: {
    solc: {
      version: "0.8.0"
	}
 }

透過以下指令執行佈署

truffle migration --network development

備註 為了要讓 geth 可以接收以 http rpc 的 transaction 發送需要修改啟動指令如下

private-chain-node1

geth --datadir ./ --networkid 123456 --port 8000 --rpc.allow-unprotected-txs --http --http.crossdomain="*" --allow-insecure-unlock --nodiscover console

private-chain-node2

geth --datadir ./ --networkid 123456 --port 8001 --rpc.allow-unprotected-txs --http --http.port=8547 --authrpc.port=8546 --http.crossdomain="*" --allow-insecure-unlock --nodiscover console

撰寫 UI 互動的部份

// Import libraries we need.
import { default as Web3 } from 'web3';
import { default as contract } from 'truffle-contract'

import voting_artifacts from '../../build/contracts/Voting.json'

var Voting = contract(voting_artifacts);

let candidates = {"Rama": "candidate-1", "Nick": "candidate-2", "Jose": "candidate-3"}

window.voteForCandidate = function(candidate) {
 let candidateName = $("#candidate").val();
 try {
  $("#msg").html("Vote has been submitted. The vote count will increment as soon as the vote is recorded on the blockchain. Please wait.")
  $("#candidate").val("");

  Voting.deployed().then(async function(contractInstance) {
   let accounts = await web3.eth.getAccounts();
   contractInstance.voteForCandidate(Web3.utils.asciiToHex(candidateName), {gas: 140000, from: accounts[0]}).then(function() {
    let div_id = candidates[candidateName];
    return contractInstance.totalVotesFor.call(Web3.utils.asciiToHex(candidateName)).then(function(v) {
     $("#" + div_id).html(v.toString());
     $("#msg").html("");
    });
   });
  });
 } catch (err) {
  console.log(err);
 }
}

$( document ).ready(function() {
  // 設定連結 RPC
  window.web3 = new Web3(new Web3.providers.HttpProvider("http://127.0.0.1:8545"));

 Voting.setProvider(web3.currentProvider);
 let candidateNames = Object.keys(candidates);
 for (var i = 0; i < candidateNames.length; i++) {
  let name = candidateNames[i];
  Voting.deployed().then(function(contractInstance) {
   contractInstance.totalVotesFor.call(Web3.utils.asciiToHex(name)).then(function(v) {
    $("#" + candidates[name]).html(v.toString());
   });
  })
 }
});

<!DOCTYPE html>
<html>
  <head>
    <title>Vote DApp</title>
    <link href='https://fonts.googleapis.com/css?family=Open+Sans:400,700' rel='stylesheet' type='text/css'>
    <link href='https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css' rel='stylesheet' type='text/css'>
  </head>
  <body class="container">
    <h1>A Simple Hello World Voting Application</h1>
    <div id="address"></div>
    <div class="table-responsive">
      <table class="table table-bordered">
        <thead>
          <tr>
            <th>Candidate</th>
            <th>Votes</th>
          </tr>
        </thead>
        <tbody>
          <tr>
            <td>Rama</td>
            <td id="candidate-1"></td>
          </tr>
          <tr>
            <td>Nick</td>
            <td id="candidate-2"></td>
          </tr>
          <tr>
            <td>Jose</td>
            <td id="candidate-3"></td>
          </tr>
        </tbody>
      </table>
      <div id="msg"></div>
    </div>
    <input type="text" id="candidate" />
    <a href="#" onclick="voteForCandidate()" class="btn btn-primary">Vote</a>
  </body>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/web3/1.8.0/web3.min.js"></script>
  <script src="https://code.jquery.com/jquery-3.1.1.slim.min.js"></script>
  <script src="index.js"></script>
</html>

執行畫面

結語

這邊為了方便所以直接使用 RPC 內部的 account

為了讓帳戶公平所以應該限制每個 account 只能投一票

並且允許使用 MetaMask

參考文獻

https://notes.andywu.tw/2018/%E5%BB%BA%E7%AB%8B%E4%BB%A5%E5%A4%AA%E5%9D%8A%E7%A7%81%E6%9C%89%E9%8F%88%E4%B8%80/


上一篇
從以太坊白皮書理解 web 3 概念 - Day28
下一篇
從以太坊白皮書理解 web 3 概念 - Day30
系列文
從以太坊白皮書理解 web 3 概念32
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言